<script type="text/javascript">
// ************************************
// Class Ball
//		The user will control the ball movements 
// with the control keys.
// ************************************
function Ball()
{
	// CONSTRUCTOR
	this.x = 0;
	this.y = 0;
	this.vx = 0;
	this.vy = 0;
	this.radius = 10;
	this.color = "#000000";
	this.iterator =0;

	// ------------------------------------
	// Ball reset
	this.ResetBall = function() 
	{
		this.x = 0;
		this.y = 0;
		this.vx = 0;
		this.vy = 0;
		this.radius = 10;
		this.color = "#000000";
		this.iterator =0;
	}
	
	// ------------------------------------
	// Ball's logic
	this.Update = function() 
	{
		if (m_controlMode==CONTROL_MODE_MOUSE)
		{
			if (m_mouseDown)
			{
				this.vx=(m_mousePositionX-this.x)/5;			
				this.vy=(m_mousePositionY-this.y)/5;
			}
		}
		else
		{
			// PROCESS INPUT
			if (m_keyPressedLeft)	this.vx--;
			if (m_keyPressedRight)	this.vx++;
			if (m_keyPressedUp)	this.vy--;
			if (m_keyPressedDown)	this.vy++;
		}

		// BALL'S LOGIC
		this.x+=this.vx;
		this.y+=this.vy;
		this.vx*=friction;		
		this.vy*=friction;
		
		// CONTROL LIMITS
		if (this.x<this.radius)	this.x=this.radius;
		if (this.y<this.radius)	this.y=this.radius;
		if (this.x+this.radius>SCREEN_WIDTH)	this.x=SCREEN_WIDTH-this.radius;
		if (this.y+this.radius>SCREEN_HEIGHT)	this.y=SCREEN_HEIGHT-this.radius;
	}

	// ------------------------------------
	// Ball's Render
	this.Render = function() 
	{
		// FILL AREA
		/*
		m_context.beginPath();
		m_context.fillStyle=this.color;
		m_context.arc(this.x,this.y,this.radius,0,Math.PI*2,true);
		m_context.closePath();
		m_context.fill();
		*/

		// DRAW BUBBLE
		m_context.drawImage(m_bubbleImage, this.x-this.radius, this.y-this.radius, this.radius*2, this.radius*2);
	}
}

// ************************************
// Class Item
//	  Represent the items that the user's ball can collide to
// ************************************
function Item(type)
{
	// var TYPE_ITEM_BUBBLE	 	= 1;
	// var TYPE_ITEM_ENEMY 		= 2;

	// CONSTRUCTOR
	this.type = type;
	this.x = 0;
	this.y = 0;
	this.vx = 0;
	this.vy = 0;
	this.radius = 0;
	this.color = "#000000";
	this.iterator = 0;
	this.isAlive = false;

	// ------------------------------------
	// Item Initialitzation
	this.Init = function(radiusIni, radiusEnd, speedIni, speedEnd) 
	{
		this.isAlive = true;
		this.radius = radiusIni+Math.floor(Math.random()*(radiusEnd-radiusIni));
		this.x = Math.floor(Math.random()*SCREEN_WIDTH);
		this.y = 0;
		this.vx = 0;
		this.vy = speedIni+Math.floor(Math.random()*(speedEnd-speedIni));
		
		// alert("Item::Init: Initialitzation of a bubble::this.radius="+this.radius);
	}

	
	// ------------------------------------
	// Reset the object to its initial values
	this.ResetItem = function() 
	{	
		this.x = 0;
		this.y = 0;
		this.vx = 0;
		this.vy = 0;
		this.isAlive = false;
	}
	
	// ------------------------------------
	// Check if the bubble collide with the bubble of the player
	this.IsCollision = function() 
	{
		if (this.isAlive)
		{
			if (Math.abs(this.x-m_ball.x)+Math.abs(this.y-m_ball.y)<m_ball.radius+this.radius)
			{
				switch (this.type)
				{
					case TYPE_ITEM_BUBBLE:
						var increaseScore = Math.floor(this.radius/5);
						m_score += (increaseScore * 100);
						m_ball.radius += increaseScore;
						this.y += SCREEN_HEIGHT;
						break;
						
					case TYPE_ITEM_ENEMY:
						m_askToLose = true;
						break;
				}
			}
		}
	}
	
	// ------------------------------------
	// Item's Render
	this.Update = function()
	{
		if (this.isAlive)
		{
			// BALL'S LOGIC
			this.x+=this.vx;
			this.y+=this.vy;
			
			// CHECK COLLISION WITH USER BALL
			this.IsCollision();
			
			// CONTROL LIMITS
			if (this.y-this.radius<SCREEN_HEIGHT)
			{
				this.isAlive = true;
			}
			else
			{
				this.isAlive = false;
			}
		}
	}

	// ------------------------------------
	// Item's Render
	this.Render = function() 
	{
		if (this.isAlive)
		{
			switch (this.type)
			{
				case TYPE_ITEM_BUBBLE:
					// DRAW BUBBLE
					m_context.drawImage(m_bubbleImage, this.x-this.radius, this.y-this.radius, this.radius*2, this.radius*2);					
					break;
					
				case TYPE_ITEM_ENEMY:
					// DRAW THORNS
					m_context.drawImage(m_thornsImage, this.x-this.radius, this.y-this.radius, this.radius*2, this.radius*2);					
					break;
			}
		}
	}
}



// ************************************
// Class MyText
//		A button with text
// ************************************
function MyText(text, x, y, height, color, font, align, baseline, enableBackground)
{
	// CONSTRUCTOR
	this.x = x;
	this.y = y;
	this.text = text;
	this.color = color;

	this.font = font;
	this.textAlign = align;
	this.textBaseline = baseline;
	
	this.heightText = height;
	
	// ------------------------------------
	// Check if the point is inside the area
	this.IsInside = function(x,y) 
	{
		switch (this.textAlign)
		{
			case 'left':
				return ((this.x-5<x)&&(this.y-5<y)&&(this.x+this.widthText+10>x)&&(this.y+this.widthText+10>y));

			case 'center':
				return ((this.x-5-(this.widthText/2)<x)&&(this.y-5<y)&&(this.x+this.widthText+10>x)&&(this.y+this.widthText+10>y));
				
			default:
				return ((this.x-5<x)&&(this.y-5<y)&&(this.x+this.widthText+10>x)&&(this.y+this.widthText+10>y));
				break;
		}
	}

	// ------------------------------------
	// Render the text
	this.Render = function() 
	{
		// ++ RENDER ++
		m_context.font = this.font;
		m_context.textAlign = this.textAlign;
		m_context.textBaseline = this.textBaseline;

		this.sizeText = m_context.measureText(this.text);
		this.widthText = this.sizeText.width;
		
		// DRAW RECTANGLE IN THE BACKGROUND OF TEXT
		if (enableBackground)
		{
			m_context.beginPath();
			m_context.strokeStyle = '#000000';
			m_context.fillStyle = this.color;
			switch (align)
			{
				case 'left':
					m_context.rect(this.x-5, this.y-5, this.widthText+10, this.heightText+10);		
					break;

				case 'center':
					m_context.rect(this.x-5-(this.widthText/2), this.y-5, this.widthText+10, this.heightText+10);
					break;
					
				case 'right':
					break;
			}
			m_context.fill();
			m_context.stroke();
		}
		
		// DRAW TEXT
		if (enableBackground)
		{
			m_context.fillStyle = '#000000';
		}
		else
		{
			m_context.fillStyle = this.color;
		}
		m_context.fillText(this.text, this.x, this.y);
		m_context.closePath();
	}
}


// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// GLOBAL CODE
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++

// ++ CONSTANTS ++
var SCREEN_WIDTH 	= 500;
var SCREEN_HEIGHT 	= 500;

var STATE_LOAD 	= 0;
var STATE_MENU	= 1;
var STATE_RUN 	= 2;
var STATE_END 	= 3;

var CONTROL_MODE_KEYBOARD 	= 0;
var CONTROL_MODE_MOUSE	 	= 1;

var TYPE_ITEM_BUBBLE	 	= 1;
var TYPE_ITEM_ENEMY 		= 2;

var TOTAL_NUMBER_BUBBLES 	= 20;
var TOTAL_NUMBER_ENEMIES 	= 20;

// ++ MEMBERS ++
var m_context;				// Document context

var m_state = STATE_LOAD;	// Main game state
var m_iterator = 0;			
var m_lastTime = 0;			// The time of the last frame
var m_difTime = 0;			// The time spent between two frames
var m_askToLose = false;
var m_score = 0;

var m_controlMode;			// Select between two control modes (KEYBOARD,MOUSE)
var m_mouseDown;			// Reports when the mouse is being pressed
var m_mousePositionX;		// X coordinate of the pressed mouse
var m_mousePositionY;		// Y coordinate of the pressed mouse

// BUTTONS
var m_playWidthMouse;
var m_playWidthKeyboard;
var m_pressHereToPlayAgain;

// IMAGES
var m_bgMenuImage;
var m_bgGameImage;
var m_bubbleImage;
var m_thornsImage;


// ACTORS
var m_ball; 

var m_bubbles;							// List of bubbles
var m_bubbleTimeGeneration=0;			// Time acumulator
var m_bubbleTimeGeneration_Limit=2000;	// Time needed between two bubble generations
var m_bubbleTimeGeneration_Factor=1.1;	// Decrease factor in the generation bubble time

var m_enemies;
var m_enemyTimeGeneration=0;			// Time acumulator
var m_enemyTimeGeneration_Limit=2000;	// Time needed between two enemies generations
var m_enemyTimeGeneration_Factor=0.8;	// Time decrease factor in the generation of an enemy 

var m_level=1;							// Game level
var m_timeLevelUp=0;					// Time acumulator
var m_timeLevelUp_Limit = 15000;		// Time to reach a level up
var m_timeLevelUp_Factor = 1.1;			// Grow time factor for level up
var m_timeoutLevelUpTitle = 0;			// Grow time factor for level up

// INPUT
var m_keyPressedLeft=false;
var m_keyPressedRight=false;
var m_keyPressedUp=false;
var m_keyPressedDown=false;
var friction=0.9;

// ------------------------------------
// OnKeyDown
document.onkeydown = function(event) 
{
     var key_pressed; 
     if(event == null)
	 {
          key_pressed = window.event.keyCode; 
     }
     else 
	 {
          key_pressed = event.keyCode; 
     }
     switch(key_pressed)
	 {
          case 37:
               m_keyPressedLeft=true;
               break; 
          case 38:
               m_keyPressedUp=true;
               break; 
          case 39:
               m_keyPressedRight=true;
               break;
          case 40:
               m_keyPressedDown=true;
               break; 
     } 
}

// ------------------------------------
// OnKeyUp
document.onkeyup = function(event) 
{
     var key_pressed; 
     if(event == null)
	 {
          key_pressed = window.event.keyCode; 
     }
     else 
	 {
          key_pressed = event.keyCode; 
     }
     switch(key_pressed)
	 {
          case 37:
               m_keyPressedLeft=false;
               break; 
          case 38:
               m_keyPressedUp=false;
               break; 
          case 39:
               m_keyPressedRight=false;
               break;
          case 40:
               m_keyPressedDown=false;
               break; 
     } 
}

// ------------------------------------
// Ask to create a new bubble
function AskNewBubble()
{
	var i=0;
	for (i=0;i<TOTAL_NUMBER_BUBBLES;i++)
	{
		var sBub = m_bubbles[i];
		if (!sBub.isAlive)
		{
			sBub.Init(5, 15, 2, 10);
			break;
		}
	}
}

// ------------------------------------
// Ask to create a new enemy
function AskNewEnemy()
{
	var i=0;
	for (i=0;i<TOTAL_NUMBER_ENEMIES;i++)
	{
		var sEnemy = m_enemies[i];
		if (!sEnemy.isAlive)
		{
			sEnemy.Init(5, 15, 1, 5);
			break;
		}
	}
}


// ------------------------------------
// Executes the logic of the bubbles increasing the level when a time limit is done
function UpdateItems()
{
	var i;
	
	// TIME GENERATOR BUBBLES
	m_bubbleTimeGeneration+=m_difTime;
	if (m_bubbleTimeGeneration>m_bubbleTimeGeneration_Limit)
	{
		m_bubbleTimeGeneration=0;
		if (m_timeoutLevelUpTitle<=0) AskNewBubble();
	}
	
	// UPDATE BUBBLE
	for (i=0;i<TOTAL_NUMBER_BUBBLES;i++)
	{
		m_bubbles[i].Update();
	}

	// TIME GENERATOR ENEMIES
	m_enemyTimeGeneration+=m_difTime;
	if (m_enemyTimeGeneration>m_enemyTimeGeneration_Limit)
	{
		m_enemyTimeGeneration=0;		
		if (m_timeoutLevelUpTitle<=0) AskNewEnemy();
	}
	
	// UPDATE ENEMIES
	for (i=0;i<TOTAL_NUMBER_ENEMIES;i++)
	{
		m_enemies[i].Update();
	}
}

// ------------------------------------
// Render the current alive bubbles
function RenderItems()
{
	var i;

	// UPDATE BUBBLE
	for (i=0;i<TOTAL_NUMBER_BUBBLES;i++)
	{
		var sBub = m_bubbles[i];
		if (sBub.isAlive)
		{
			sBub.Render();
		}
	}
	
	// UPDATE ENEMIES
	for (i=0;i<TOTAL_NUMBER_ENEMIES;i++)
	{
		var sEne = m_enemies[i];
		if (sEne.isAlive)
		{
			sEne.Render();
		}
	}
}


// ------------------------------------
// Executes the logic of the bubbles increasing the level when a time limit is done
function ResetGame()
{
	var i;

	m_lastTime = 0;
	m_difTime = 0;
	m_askToLose = false;
	m_score = 0;

	m_bubbleTimeGeneration=0;
	m_bubbleTimeGeneration_Limit=2000;
	m_bubbleTimeGeneration_Factor=1.1;

	m_enemyTimeGeneration=0;
	m_enemyTimeGeneration_Limit=2000;
	m_enemyTimeGeneration_Factor=0.8;

	m_level=1;
	m_timeLevelUp=0;
	m_timeLevelUp_Limit = 15000;
	m_timeLevelUp_Factor = 1.1;
	m_timeoutLevelUpTitle = 0;
	
	m_mousePositionX=0;
	m_mousePositionY=0;
	m_mouseDown = false; 
	
	m_ball.ResetBall();
	
	for (i=0;i<TOTAL_NUMBER_BUBBLES;i++)
	{
		m_bubbles[i].ResetItem();
	}
	
	
	for (i=0;i<TOTAL_NUMBER_ENEMIES;i++)
	{
		m_enemies[i].ResetItem();
	}
}

// ------------------------------------
// Change the main state of the game
function ChangeState(newState)
{
	m_state = newState;
	m_iterator = 0;
}

// ------------------------------------
// Main logic loop
function Update()
{
	if (m_iterator<100) m_iterator++;

	// CALCULATE TIME 
	var currentTime = new Date()
	if (m_lastTime == 0)
	{
		m_lastTime=currentTime.getTime();
		m_difTime=0;
	}
	else
	{
		m_difTime = currentTime.getTime() - m_lastTime;		
		m_lastTime = currentTime.getTime();
	}

	// LEVEL UP
	m_timeLevelUp+=m_difTime;
	if (m_timeLevelUp>m_timeLevelUp_Limit)
	{
		m_level++;
		m_timeoutLevelUpTitle = 2000;
		m_timeLevelUp = 0;		
		m_timeLevelUp_Limit*=m_timeLevelUp_Factor;
		
		// CHANGE BUBBLE RATE
		m_bubbleTimeGeneration_Limit*=m_bubbleTimeGeneration_Factor;
	
		// CHANGE ENEMY RATE
		m_enemyTimeGeneration_Limit*=m_enemyTimeGeneration_Factor;
	}
	if (m_timeoutLevelUpTitle>0)
	{
		m_timeoutLevelUpTitle -= m_difTime;
	}

	// EVALUATE MAIN GAME STATE
	switch (m_state)
	{
		/////////////////////////
		case STATE_LOAD:
			switch (m_iterator)
			{
				case 1:
					// INIT GRAPHICS
					m_context=game_area.getContext('2d');

					// LOAD IMAGES
					m_bgGameImage = new Image();
					m_bgGameImage.src = 'images/bg_game.jpg';
					
					m_bubbleImage = new Image();
					m_bubbleImage.src = 'images/bubble.png';
					
					m_thornsImage = new Image();
					m_thornsImage.src = 'images/thorns.png';
					
					m_bgMenuImage = new Image();
					m_bgMenuImage.onload = function() {  m_iterator=100; };
					m_bgMenuImage.src = 'images/bg_menu.jpg';

					// INIT GAME ELEMENTS
					m_ball = new Ball();
					
					// BUBBLES INIT
					m_bubbles=new Array(TOTAL_NUMBER_BUBBLES);
					var i;
					for (i=0;i<TOTAL_NUMBER_BUBBLES;i++) m_bubbles[i]=new Item(TYPE_ITEM_BUBBLE);
					
					// BUBBLES ENEMIES
					m_enemies=new Array(TOTAL_NUMBER_ENEMIES);
					for (i=0;i<TOTAL_NUMBER_ENEMIES;i++) m_enemies[i]=new Item(TYPE_ITEM_ENEMY);
					
					// ENABLE MOUSE LISTENER
					m_mouseDown = false;
					m_context.canvas.addEventListener('mousedown', function(event) 
					{ 
						m_mouseDown = true; 
						m_mousePositionX=event.clientX;
						m_mousePositionY=event.clientY;
					}, false);
					m_context.canvas.addEventListener('mouseup', function(event) 
					{ 
						m_mouseDown = false; 
					}, false);
					m_context.canvas.addEventListener('mousemove', function(event) {
					  if (m_mouseDown) {
						m_mousePositionX=event.clientX;
						m_mousePositionY=event.clientY;
					   }
					}, false);
					break;

				case 2:
					m_iterator=1;
					break;
					
				default:
					// INIT MENU
					ChangeState(STATE_MENU);
					break;
					
			}
			break;
			
		/////////////////////////
		case STATE_MENU:
			switch (m_iterator)
			{
				case 1:
					// DRAW BACKGROUND IMAGE
					m_context.drawImage(m_bgMenuImage, 0, 0);
				
					// INIT TEXT
					var titleGame = new MyText('TUTORIAL GAME HTML5',SCREEN_WIDTH/2,SCREEN_HEIGHT/6,24,'#000000', '30px sans-serif', 'center', 'top', false);
					m_playWidthMouse = new MyText('PLAY WITH MOUSE',SCREEN_WIDTH/2,2.5*SCREEN_HEIGHT/6,24,'#00ffff', '20px sans-serif', 'center', 'top', true);
					m_playWidthKeyboard = new MyText('PLAY WITH KEYBOARD',SCREEN_WIDTH/2,3.5*SCREEN_HEIGHT/6,24,'#00ff00', '20px sans-serif', 'center', 'top', true);
					
					// RENDER TEXT
					titleGame.Render();
					m_playWidthMouse.Render();
					m_playWidthKeyboard.Render();
					break;
			}
			
			// CHECK BUTTON SELECTION
			if (m_mouseDown)
			{
				if (m_playWidthMouse.IsInside(m_mousePositionX,m_mousePositionY))
				{
					m_controlMode=CONTROL_MODE_MOUSE;
					m_mouseDown=false;
					ChangeState(STATE_RUN);
				}
				if (m_playWidthKeyboard.IsInside(m_mousePositionX,m_mousePositionY))
				{
					m_controlMode=CONTROL_MODE_KEYBOARD;
					m_mouseDown=false;
					ChangeState(STATE_RUN);
				}
			}
			break;
		
		/////////////////////////
		case STATE_RUN:
			
			switch (m_iterator)
			{
				case 1:
					// ++ INIT GAME ELEMENTS ++
					ResetGame();
					
					m_ball.x = SCREEN_WIDTH/2;
					m_ball.y = SCREEN_HEIGHT/2;
					
					m_timeoutLevelUpTitle = 2000;
					break;
			}

			// ++ LOGIC ++
			m_ball.Update();
			UpdateItems();
			
			// ++ RENDER ++
			m_context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
			m_context.drawImage(m_bgGameImage, 0, 0);
			m_ball.Render();
			RenderItems();

			// RENDER SCORE
			var titleScore = new MyText('SCORE: '+m_score,10,SCREEN_HEIGHT-30,24,'#ffffff', '20px sans-serif', 'left', 'top', false);
			titleScore.Render();
			
			// LEVEL UP TITLE
			if (m_timeoutLevelUpTitle>0)
			{
				var titleLevelUp = new MyText('LEVEL '+m_level, SCREEN_WIDTH/2, (SCREEN_HEIGHT/4), 24, '#ffffff', '50px sans-serif', 'center', 'top', false);
				titleLevelUp.Render();
			}
			
			// ++ END CONDITIONS ++
			if (m_askToLose)
			{
				m_askToLose=true;
				ChangeState(STATE_END);
			}
			break;
			
		/////////////////////////
		case STATE_END:
			switch (m_iterator)
			{
				case 1:
					// INIT TEXT
					var titleGameOver = new MyText('GAME OVER',SCREEN_WIDTH/2,SCREEN_HEIGHT/6,24,'#ff0000', '30px sans-serif', 'center', 'top', true);
					var titleFinalScore = new MyText('YOUR SCORE: '+m_score,SCREEN_WIDTH/2,SCREEN_HEIGHT/2,24,'#ffffff', '30px sans-serif', 'center', 'top', false);
					m_pressHereToPlayAgain = new MyText('Press here to go to menu screen',SCREEN_WIDTH/2,5*SCREEN_HEIGHT/6,24,'#00ff00', '20px sans-serif', 'center', 'top', true);
					
					// RENDER TEXT
					titleGameOver.Render();
					titleFinalScore.Render();
					m_pressHereToPlayAgain.Render();
					break;
			}
			
			// CHECK BUTTON SELECTION
			if (m_mouseDown)
			{
				if (m_pressHereToPlayAgain.IsInside(m_mousePositionX,m_mousePositionY))
				{
					m_mouseDown=false;
					ChangeState(STATE_MENU);
				}
			}
			break;
	}
	document.write.(m_mousePositionX+','+m_mousePositionY);
}

// SET MAIN LOOP
setInterval(Update, 30);
 
</script>
 
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#game_area{
     background-color:#f5f5f5;
     border:1px solid #e5e5e5;
}
</style>
 
</head>
<body>
     <canvas id="game_area" width="500" height="500"></canvas>
</body>
</html>